﻿using System;
using System.Collections.Generic;
using MugenInjection.Core;
using MugenInjection.Delegates;
using MugenInjection.Infrastructure;
using MugenInjection.Interface;

namespace MugenInjection.Scope
{
    /// <summary>
    ///     Represents a base class for scope lifecycle all operation is thread-safety.
    /// </summary>
    public abstract class ScopeLifecycleBase : DisposableObject, IScopeLifecycle
    {
        #region Fields

        private readonly ICollection<Action<object, bool>> _releaseObjectActions;
        protected readonly object Locker = new object();
        private IScopeLifecycleContext _context;

        #endregion

        #region Constructors

        /// <summary>
        ///     Initializes a new instance of the <see cref="ScopeLifecycleBase" /> class.
        /// </summary>
        protected ScopeLifecycleBase()
        {
            _releaseObjectActions = new List<Action<object, bool>>();
        }

        #endregion

        #region Methods

        /// <summary>
        ///     Trying to release the specified object with notification.
        /// </summary>
        /// <param name="obj">The specified object.</param>
        protected void TryReleaseObjectWithNotification(object obj)
        {
            TryReleaseObjectWithNotification(obj, IsNeedDispose());
        }

        /// <summary>
        ///     Trying to release the specified object with notification.
        /// </summary>
        /// <param name="obj">The specified object.</param>
        /// <param name="isNeedDispose">Indicating that need trying dispose objects.</param>
        protected void TryReleaseObjectWithNotification(object obj, bool isNeedDispose)
        {
            foreach (var releaseObjectAction in _releaseObjectActions)
                releaseObjectAction(obj, isNeedDispose);
            if (!isNeedDispose) return;
            var disposable = obj as IDisposable;
            if (disposable == null) return;
            disposable.Dispose();
        }

        /// <summary>
        ///     Resolves instance for the specified <see cref="IBindingContext" />.
        /// </summary>
        /// <returns>An instance of the service.</returns>
        protected object ResolveCurrentService()
        {
            return _context.ResolveCurrentService();
        }

        /// <summary>
        ///     Indicating that need dispose object.
        /// </summary>
        /// <returns></returns>
        private bool IsNeedDispose()
        {
            if (Context == null || Context.Binding == null)
                return false;
            bool needDispose;
            return
                Context.Binding.Settings.TryGet(SpecialParameterKeys.TryDisposeObjectSetting, out needDispose) &&
                needDispose;
        }

        /// <summary>
        ///     Resolves instance for the specified <see cref="IBindingContext" /> from scope.
        /// </summary>
        /// <returns>An instance of the service.</returns>
        protected abstract object GetObjectFromScope();

        /// <summary>
        ///     Gets all created objects in the current scope.
        /// </summary>
        protected abstract IList<object> GetObjectsFromScope();

        /// <summary>
        ///     Clears the current scope.
        /// </summary>
        protected abstract void ClearScope();

        #endregion

        #region Overrides of DisposableObject

        /// <summary>
        ///     Releases resources held by the object.
        /// </summary>
        protected override void Dispose(bool disposing)
        {
            if (disposing)
                ((IScopeLifecycle)this).ReleaseObjects(IsNeedDispose());
            base.Dispose(disposing);
        }

        #endregion

        #region Implementation of IScopeLifecycle

        /// <summary>
        ///     Gets or sets the <see cref="IScopeLifecycleContext" />.
        /// </summary>
        public IScopeLifecycleContext Context
        {
            get { return _context; }
            set
            {
                lock (Locker)
                    _context = value;
            }
        }

        /// <summary>
        ///     Gets the actions that should be called before instances are deactivated via the binding.
        /// </summary>
        public ICollection<Action<object, bool>> ReleaseObjectActions
        {
            get
            {
                lock (Locker)
                    return _releaseObjectActions;
            }
        }

        /// <summary>
        ///     Resolves instance for the specified <see cref="IBindingContext" /> from scope.
        /// </summary>
        /// <returns>An instance of the service.</returns>
        object IScopeLifecycle.GetObjectFromScope()
        {
            return GetObjectFromScope();
        }

        /// <summary>
        ///     Releases all objects in current scope.
        /// </summary>
        /// <param name="isNeedDisposeObject">Indicating that need trying dispose objects.</param>
        public void ReleaseObjects(bool isNeedDisposeObject)
        {
            lock (Locker)
            {
                foreach (object objToRelease in GetObjectsFromScope())
                    TryReleaseObjectWithNotification(objToRelease, isNeedDisposeObject);
                ClearScope();
            }
        }

        #endregion
    }
}